package com.funcablaze.tpxterm.func.net;

import com.jcraft.jsch.*;
import com.techsenger.jeditermfx.core.TtyConnector;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SSHConnector implements TtyConnector {
	
	private static final ArrayList<SSHConnector> sshConnectorList = new ArrayList<>();
	public final Session ssh_session;
	// Shell
	private ChannelShell channel_shell;
	// SFTP
	private ChannelSftp channel_sftp;
	private final ExecutorService executor = Executors.newCachedThreadPool();
	public final String hostName, username;
	public final int port;
	private InputStream inputStream;
	private OutputStream outputStream;
	private final Charset charset = StandardCharsets.UTF_8;
	
	public SSHConnector(SSHConfig sshConfig) throws JSchException, IOException {
		sshConnectorList.add(this);
		this.hostName = sshConfig.host;
		this.username = sshConfig.username;
		this.port = sshConfig.port;
		JSch jsch = new JSch();
		this.ssh_session = jsch.getSession(username, hostName, port);
		this.ssh_session.setConfig("StrictHostKeyChecking", "no");
		this.ssh_session.setPassword(sshConfig.password);
	}
	
	public void link() throws JSchException, IOException {
		this.ssh_session.connect();
		this.channel_shell = (ChannelShell) ssh_session.openChannel("shell");
		this.inputStream = channel_shell.getInputStream();
		this.outputStream = channel_shell.getOutputStream();
		this.channel_shell.connect();
		this.channel_sftp = (ChannelSftp) ssh_session.openChannel("sftp");
		this.channel_sftp.connect();
	}
	
	public static SSHConnector getSSHConnector(String host, int port, String username) {
		for (SSHConnector sshConnector : sshConnectorList) {
			if (sshConnector.ssh_session.getHost().equals(host)
					&& sshConnector.ssh_session.getPort() == port
					&& sshConnector.ssh_session.getUserName().equals(username)
			) {
				return sshConnector;
			}
		}
		return null;
	}
	
	@Override
	public void close() {
		if (channel_shell != null && channel_shell.isConnected())
			channel_shell.disconnect();
		if (channel_sftp != null && channel_sftp.isConnected())
			channel_sftp.disconnect();
		try {
			inputStream.close();
			outputStream.close();
		} catch (IOException ignore) {
		}
		executor.shutdown();
		sshConnectorList.remove(this);
	}
	
	@Override
	public String getName() {
		return "SSH: " + hostName;
	}
	
	@Override
	public int read(char[] buf, int offset, int length) throws IOException {
		byte[] tmp = new byte[length];
		int len = inputStream.read(tmp, 0, length);
		if (len > 0) {
			String decoded = new String(tmp, 0, len, charset);
			char[] chars = decoded.toCharArray();
			System.arraycopy(chars, 0, buf, offset, chars.length);
			return chars.length;
		}
		return 0;
	}
	
	@Override
	public void write(byte[] bytes) throws IOException {
		outputStream.write(bytes);
		outputStream.flush();
	}
	
	@Override
	public boolean isConnected() {
		return ssh_session.isConnected() && channel_shell.isConnected();
	}
	
	@Override
	public void write(String string) throws IOException {
		write(string.getBytes(charset));
	}
	
	@Override
	public int waitFor() {
		return 0;
	}
	
	@Override
	public boolean ready() throws IOException {
		return inputStream.available() > 0;
	}
	
	public static class SSHConfig {
		private final String host;
		private final int port;
		private final String username;
		private final String password;
		
		public SSHConfig(String host, int port, String username, String password) {
			this.host = host;
			this.port = port;
			this.username = username;
			this.password = password;
		}
	}
	
	public ChannelSftp getChannel_sftp() {
		return channel_sftp;
	}
}